﻿
using System;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Imaging;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Data
{
    public abstract class BaseInfiniteLight : ILight, INewLight
    {
        public const string SkyLightSunDir = "SkyLight.SunDir";
        public const string SkyLightTurbulence = "SkyLight.Turbulence";

        public abstract string Name { get; }

        public bool IsDelta
        {
            get
            {
                return true;
            }
        }


        public EmissionProfile Profile { get; set; }

        public abstract ISpectrum Le(ref Vector dir);
        public virtual RgbSpectrumInfo Emittance(ref Vector dir, out float pdf)
        {
            var res = new RgbSpectrumInfo(((RgbSpectrum)this.Le(ref dir)));
            pdf = dir.z*MathLab.INVPI*res.y();
            return res;
        }

        public virtual void EvaluateShadow(ref Point point, ref Normal n, float u0, float u1, float u2, ref LightSample result)
        {
#if VERBOSE
            try{
#endif
            if (result == null)
                result = new LightSample();

            if (true)
            {
                var wi = MC.CosineSampleHemisphere(u1, u2);

                Vector v1, v2;
                Vector.CoordinateSystem(ref n, out v1, out v2);
                //Console.WriteLine("Should share frame(ONB) with bsdf somehow");

                wi = new Vector(
                    v1.x*wi.x + v2.x*wi.y + n.x*wi.z,
                    v1.y*wi.x + v2.y*wi.y + n.y*wi.z,
                    v1.z*wi.x + v2.z*wi.y + n.z*wi.z).Normalize();

                result.LightRay = new RayData(ref point, ref wi, 1e-4f, float.MaxValue);
                result.Distance = 1e4f;
                result.Spectrum = Le(ref wi);
                
                result.Pdf = Math.Abs(wi.z * MathLab.INVPI);

                //var i = ((RgbSpectrumInfo) result.Spectrum).y();
                //result.Pdf *= i;

            }
            else
            {
                Vector wi = MC.UniformSampleSphere(u0, u1);

                result.LightRay = new RayData(ref point, ref wi, 1e-4f, float.MaxValue);
                result.Pdf = 1f / (4f * MathLab.M_PI);
                result.Spectrum =  Le(ref wi);

                //var i = ((RgbSpectrumInfo)result.Spectrum).y();
                //result.Pdf *= i;
            }
#if VERBOSE
            }catch(Exception ex) {
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#endif
        }

        public virtual void EvaluatePhoton(IRayEngineScene scen, float u0, float u1, float u2, float u3, float u4, out LightSample result)
        {
            result = new LightSample();
            var scene = (RayEngineScene) scen;

            var worldCenter = scene.SceneGeometry.BoundingSphereCenter;
            var worldRadius = scene.SceneGeometry.BoundingSphereRadius * 1.01f;

            var p1 = worldCenter + worldRadius * MC.UniformSampleSphere(u0, u1);
            var p2 = worldCenter + worldRadius * MC.UniformSampleSphere(u2, u3);

            // Construct ray between p1 and p2
            result.LightRay = new RayData(p1, (p2 - p1).Normalize(), 1e-4f, 1e4f);

            // Compute InfiniteAreaLight ray weight
            Vector toCenter = (worldCenter - p1).Normalize();
            var rayDir = result.LightRay.Dir;
            float costheta = MathLab.AbsDot(ref toCenter, ref rayDir);
            result.Pdf = costheta / (4f * MathLab.M_PI * MathLab.M_PI * worldRadius * worldRadius);

            result.Spectrum = Le(ref result.LightRay.Dir);
        }


        public virtual RgbSpectrum SampleBackground(ref Vector dir)
        {
            var lt = Le(ref dir);
            if (GlobalConfiguration.Instance.SpectralRendering)
                return (ColorFactory.ToRgb(ref lt));
            return (RgbSpectrum)(Le(ref dir));
        }


        public void Le(ref Vector dir, ref ISpectrum le)
        {
            var s = this.Le(ref dir);
            le.Mul(ref s);
        }

        public void EvaluateShadow(Core.Surface.BaseBxdf bsdfSample, float u0, float u1, float u2, ref LightSample result)
        {
#if VERBOSE
            try
            {
#endif
                if (result == null)
                    result = new LightSample();

                if (true)
                {
                    var wi = MC.CosineSampleHemisphere(u1, u2);

                    wi = bsdfSample.Frame.ToWorld(ref wi);

                    result.LightRay = new RayData(ref bsdfSample.HitPoint.HitPoint, ref wi, 1e-4f, float.MaxValue);
                    result.Distance = 1e4f;
                    result.Spectrum = Le(ref wi);
                    result.Pdf = wi.z * MathLab.INVPI;

                    //var i = ((RgbSpectrumInfo) result.Spectrum).y();
                    //result.Pdf *= i;

                }
                else
                {
                    Vector wi = MC.UniformSampleSphere(u0, u1);

                    result.LightRay = new RayData(ref bsdfSample.HitPoint.HitPoint, ref wi, 1e-4f, float.MaxValue);
                    result.Pdf = 1f / (4f * MathLab.M_PI);
                    result.Spectrum = Le(ref wi);

                    //var i = ((RgbSpectrumInfo)result.Spectrum).y();
                    //result.Pdf *= i;
                }
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#endif
        }


        public void SampleSpatialDirect(ref Point point, ref Normal n, float u0, float u1, float u2, ref LightSample ls)
        {
            var wi = MC.CosineSampleHemisphere(u1, u2);

            Vector v1, v2;
            Vector.CoordinateSystem(ref n, out v1, out v2);
            //Console.WriteLine("Should share frame(ONB) with bsdf somehow");

            ls.LightRay.Dir = new Vector(
                v1.x * wi.x + v2.x * wi.y + n.x * wi.z,
                v1.y * wi.x + v2.y * wi.y + n.y * wi.z,
                v1.z * wi.x + v2.z * wi.y + n.z * wi.z).Normalize();

            ls.Distance = 1e4f;
            ls.Pdf = wi.z * MathLab.INVPI;
        }

        public void SampleSpatialAdjoint(IRayEngineScene scen, float u0, float u1, float u2, float u3, float u4, out Vector dir, out float distance, out float pdf)
        {
            var scene = (RayEngineScene)scen;

            var worldCenter = scene.SceneGeometry.BoundingSphereCenter;
            var worldRadius = scene.SceneGeometry.BoundingSphereRadius * 1.01f;

            var p1 = worldCenter + worldRadius * MC.UniformSampleSphere(u0, u1);
            var p2 = worldCenter + worldRadius * MC.UniformSampleSphere(u2, u3);

            // Construct ray between p1 and p2
            dir= (p2 - p1).Normalize();

            // Compute InfiniteAreaLight ray weight
            Vector toCenter = (worldCenter - p1).Normalize();
            var rayDir= dir;
            float costheta = MathLab.AbsDot(ref toCenter, ref rayDir);
            distance = 1e4f;
            pdf = costheta / (4f * MathLab.M_PI * MathLab.M_PI * worldRadius * worldRadius);
        }
    }
}